import socket
import threading
import traceback
import random
from peer import *
import time
import datetime

global clients
clients={}
num_of_neighbor=7
global initialized_flag
initialized_flag=False
init_forwarded=False
waiting=False
#neighbors_conn=[]
clustering_group={}
fini_clients_num=0



def timeout():
    global initialized_flag
    initialized_flag=False
    print "mutex free"
    



# Re-directing Handler
def handlepeer(conn):
    host, port = conn.s.getpeername()
    #print "connected by"
    #print host, 
    #print port
    while True:
        #data = clientsock.recv(1024)
        msgtype, msg = conn.recvdata()
        if msg != None:
            global initialized_flag,clustering_group,init_forwarded
            node_lost=[]
            # If recv message is GETN
            if msgtype=='GETN':
          
              if (initialized_flag or waiting):
                print "I won't forward others GETN msg\n\n"
                pass
              else:
                
                initialized_flag=True
                
                my_timer = threading.Timer(60.0, timeout)
                my_timer.start()

                print "Set initialized flag to true"
                master = conn.s.getpeername()
                print "This is the master",str(master)
                try:
                    conn.senddata('MUTX','')
                    print "Mutex locked by",master
                except:
                    print "Master is down"
                    initialized_flag=False
                    
                
                clustering_group={}
                response=[]
                if len(clients)<= num_of_neighbor:
                    for one in clients:
                        if clients[one].s.getpeername() == conn.s.getpeername():
                            continue
                        else:
                            tmp_conn=clients[one]
                            host_port=tmp_conn.s.getpeername()
                            response.append(host_port)
                            print host_port,"added to initial clustering_group"
                            clustering_group[host_port]=tmp_conn
                            
                            #neighbors_conn.append(tmp_conn)
                else:
                    tmp_clients={}
                    for one in clients:
                        tmp_clients[one]=clients[one]
                    clients_list=tmp_clients.items()
                    for i in range(num_of_neighbor):
                        num=random.randint(0, num_of_neighbor-1-i)
                        tmp_conn=clients_list[num][1]
                        print conn.s.getpeername(), tmp_conn.s.getpeername()
                        if conn.s.getpeername()==tmp_conn.s.getpeername():
                            continue
                        else:
                            host_port=tmp_conn.s.getpeername()
                            response.append(host_port)
                            #neighbors_conn.append(tmp_conn)
                            clustering_group[host_port]=tmp_conn
                            clients_list.pop(num)
                
                print "Before CLUESTERING GROUP IS",clustering_group
                
                for one in clustering_group:
                    print "Prima responde",response
                    tmp_response=[x for x in response]
                    tmp_response.append((host,port))
                    print "tmp_response",tmp_response
                    tmp_host_port=clustering_group[one].s.getpeername()
                    print "Tmp_host_port",tmp_host_port
                    tmp_response.remove(tmp_host_port)
                    
              
                    
                    msg_type='NEBO'
                    msg=str(tmp_response)
                    #print tmp_response
                    try:
                        clustering_group[one].senddata(msg_type,msg)
                        print "Msg NEBO sent to", one, " His neighbors are --> ", msg, "\n"
                    except Node_failed:
                        node_lost.append(one)     
                        print 'node', node_lost, 'is lost'
                        
                if len(node_lost)!=0:
                    for one in node_lost:
                        clustering_group.pop(one)
                    for one in clustering_group:
                        clustering_group[one].senddata('LOST', str(node_lost))
                        print "Inviato node lost --> ",node_lost
                    
                        
            
                #neighbors_conn.append(conn)
                
                clustering_group[(host,port)]=conn
                print "Clustering group length =",
                print len(clustering_group)
                print "CLUESTERING GROUP IS",clustering_group
                response_str=str(response)
                print response
                try:
                    conn.senddata('NEBO', response_str)
                    print "Msg NEBO sent to the Master. His neighbors are -->  ", response_str, "\n"
                except Node_failed:
                    clustering_group.pop(tuple([host,port]))     
                    for one in clustering_group:
                        clustering_group[one].senddata('LOST', str([[host,port]]))
            
            elif msgtype=='STUC':
                #initialized_flag=False
                print "One node is stuck"
                #conn.s.getpeername():
                host_port=conn.s.getpeername()
                #response.append(host_port)
                clustering_group.pop(host_port)
                print host_port,"removed from initial clustering_group"
                print "clustering group is",clustering_group
                #clustering_group[host_port]=tmp_conn
                node_lost.append(host_port)
                clients.pop(host_port)     
                print 'node', node_lost, 'is stuck'
                for one in clustering_group:                
                    clustering_group[one].senddata('LOST', str(node_lost))
                    print "Inviato node lost --> ",node_lost
 

            elif msgtype=='FMUT':
                initialized_flag=False
                print "Mutex is unlocked"
        
            # If recv message is FINI
            else:
                if msgtype=='FINI':
                    global fini_clients_num
                    fini_clients_num=fini_clients_num+1
                    print "Number of clients that have finished =", fini_clients_num
                    print len(clustering_group)
                    if (not fini_clients_num<len(clustering_group) and initialized_flag==True):
                        ends = threading.Thread( target = ending, args = [] ) 
                        ends.start()
                        
                else:
                    if msgtype=='INIT':
                        init_forwarded=True
                    if msgtype=='STAR':
                        my_timer.cancel()
                        print "Star msg recv"
                    dst = eval(msg)[-1]
                    #print "dst is",dst
                    data=eval(msg)[0:-1]
                    print 'data is',data
                    data.append([host,port])
                    #print 'data to send is', data
                    global node_down
                    node_down=[]
                    try:
                        clients[tuple(dst)].senddata(msgtype, str(data))
                        print "forwarding", msgtype, "message from ", host, port, 'to ', dst
                        print 'msg is', msg, "\n"
                    except Node_failed:
                        if (clustering_group.has_key(tuple(dst))):
                            clustering_group.pop(tuple(dst)) 
                        clients.pop(tuple(dst))
                        node_down.append(tuple(dst))
                        
                        for one in clustering_group:
                            try:
                                clustering_group[one].senddata('LOST', str(node_down))
                                print "LOST --> ", node_down                                
                            except:
                                node_down.append(one)                                
                                print "Found other node LOST --> ", node_down
                    except KeyError:
                        print "KeyError"
                        pass
                    

# Finishing
def ending():
    global clustering_group,initialized_flag,init_forwarded,fini_clients_num,waiting
    print "Finished but waiting"
    initialized_flag=False
    waiting=True
    time.sleep(10)
    for one in clustering_group:
        try:
            clustering_group[one].senddata('ENDD', str(clustering_group.keys()))
        except:
            print "TEST"
        pass
    print "ENDD Sent to everyone"
    initialized_flag=False
    init_forwarded=False
    
    #clustering_group={}
    fini_clients_num=0
    waiting=False
    



                    
# Testing Aliveness
def conn_test():
    global clustering_group, clients,initialized_flag,fini_clients_num,init_forwarded
    while True:
        time.sleep(5)
        tmp={}
        for one in clients:
            print one,"is alive"
            tmp[one]=clients[one]
            
        node_lost=[]
        for one in tmp:
            try:
                tmp[one].senddata('TEST', '')
            except Node_failed:
                if (clustering_group.has_key(one)):
                    clustering_group.pop(one)
                clients.pop(one)
                node_lost.append(one)     
                print '\nNode', node_lost, 'is lost'
        if len(node_lost)!=0 :
            for one in clients:
                print "Msg some one is lost sent to --> ", one
                try:
                    clients[one].senddata('LOST', str(node_lost))
                except:
                    pass
        if (not fini_clients_num<len(clustering_group) and initialized_flag==True):
            ends = threading.Thread( target = ending, args = [] ) 
            ends.start()
            '''for one in clustering_group:
                try:
                    clustering_group[one].senddata('ENDD', str(clustering_group.keys()))
                except:
                    pass
            print "ENDD Sent to everyone"
            initialized_flag==False
            init_forwarded=False
            fini_clients_num=0'''
        if len(clients)==0:
            
            fini_clients_num=0
            initialized_flag=False
            init_forwarded=False
            clustering_group={}
            print "no one is initialized", initialized_flag

# Main () - Listenign for Connection
port = 8080
backlog = 5
s = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) 
s.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 ) 
s.bind( ( '', port ) ) 
print "I am binding to the port",
print port
print "connection up limit",
print backlog
s.listen( backlog )
conn_test_thread=threading.Thread(target=conn_test,args=[])
conn_test_thread.start()









while True: 
    try: 
        dynamic=False
        clientsock, clientaddr = s.accept() 
        conn = BTPeerConnection(None, clientaddr[0], clientaddr[1], clientsock, debug=False)
        clients[clientaddr] = conn
        print "Initialized flag is",initialized_flag
        print "Init forward state is",init_forwarded
        
        if (init_forwarded==True and not waiting and dynamic):
            print clustering_group
            try:
                conn.senddata('OLD ', str(clustering_group.keys()) )
            except Node_failed:
                pass
            else:
                node_lost=[]
                for one in clustering_group:
            #global node_lost
                    try:
                        clustering_group[one].senddata('ADDN',str([tuple(conn.s.getpeername())]))
                        print "send ADDN to", str([list(clustering_group[one].s.getpeername())])
                    except Node_failed:
                        node_lost.append(one)     
                        print 'node', one, 'is lost'
                    
                        for one in clustering_group:
                            try:
                                clustering_group[one].senddata('LOST', str(node_lost))
                            except Node_failed:
                                pass
                if len(node_lost)!=0:
                    print "NODES LOST",node_lost
                    for one in node_lost:
                        clustering_group.pop(one)
                        clients.pop(one)
                    print "CLUESTERING GROUP IS",clustering_group
                clustering_group[clientaddr]=conn
        print 'node ',
        print clientaddr, 
        print ' is connecting'
        print clients
        clientsock.settimeout(None) #blocking mode
        t = threading.Thread( target = handlepeer, args = [ conn ] ) 
        t.start()
        
    except: 
        traceback.print_exc() 
        continue              
